/*
 * Created on 10 Jul 2008 by Andrew White
 */

package com.volatileshooter.networking.server.client;

import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeMap;

import com.volatileshooter.networking.io.GameChannel;
import com.volatileshooter.networking.io.GamePacket;
import com.volatileshooter.networking.server.util.Log;

/*
 * Handles adding and removing Clients.
 */
public class ClientHandler {

    /* server client ID */
    public static final short SERVER_ID = -1;

    /* clients map (mapped using the Client Id as the key) */
    private TreeMap<Short, Client> clients;

    /* client Id list (for generating IDs) */
    private LinkedList<Short> clientIdList;

    /**
     * Creates a new instance of ClientHandler.
     * 
     * @param maxClients
     *            The maximum number of clients the server allows.
     */
    public ClientHandler(short maxClients) {
        // create client list
        clients = new TreeMap<Short, Client>();

        // create a new list of possible client IDs
        clientIdList = new LinkedList<Short>();
        for (short i = 0; i < maxClients; i++)
            clientIdList.add(new Short(i));
    }

    /**
     * Creates a new Client and adds to the Client list with a unique ID, if the
     * client cannot be added because the client list is full then NULL is
     * returned. If the client already exists (i.e. has matching
     * InetSocketAddress) then that client object will be returned, but will NOT
     * be added again.
     * 
     * @param name
     *            Name of the player
     * @param addr
     *            IP address and port of the client
     * 
     * @return The client that has just been added to the list
     */
    public Client add(String name, InetSocketAddress addr) {
        // check if any IDs left, i.e. have we reached max number of clients
        if (clientIdList.isEmpty())
            return null;

        // check if client already exists
        Client client = getClient(addr);
        if (client == null) {
            // create a new client with an available ID
            client = new Client(clientIdList.pop(), name, addr);
            // add new client to the list
            clients.put(client.id, client);
        }

        return client;
    }

    /**
     * Removes a client from the list and makes this ID available for use.
     * 
     * @param id
     *            The unique client Id
     */
    public void remove(short id) {
        // remove client from list
        clients.remove(id);

        // add the client ID back into the ID list
        clientIdList.push(id);
    }

    /**
     * Returns the client with that ID, if no client exists with that ID then
     * NULL is returned.
     * 
     * @param id
     *            Client ID
     * @return The client with that ID
     */
    public Client getClient(short id) {
        return clients.get(id);
    }

    /**
     * Returns the client with matching IP address and port, if no client exists
     * with that ID then NULL is returned.
     * 
     * @param addr
     *            InetSocketAddress of client
     * @return The client with that address
     */
    public Client getClient(InetSocketAddress addr) {
        // Loop through iterator of clients
        Iterator<Client> it = clients.values().iterator();
        while (it.hasNext()) {
            Client client = it.next();

            // check if client exists with matching address
            if (client.addr.equals(addr)) {
                // return client
                return client;
            }
        }

        // no match exists, return NULL
        return null;
    }

    /**
     * Sends a GamePacket via a GameChannel to the client with the given ID. If
     * no client exists with that ID then the function returns without
     * attempting to send the packet. It is important to note that the client ID
     * of the packet is not set by this function.
     * 
     * @param id
     *            Client ID to send the packet too (client ID of packet is not
     *            set)
     * @param packet
     *            GamePacket to send
     * @param channel
     *            GameChannel to use to send the packet
     */
    public void sendPacketTo(short id, GamePacket packet, GameChannel channel) {
        // get client from client list
        Client client = getClient(id);
        if (client == null) {
            Log.warning("Attempted to send packet to client " + id
                    + " that does not exist", null);
            return;
        }

        // set packet address
        packet.addr = client.addr;

        // send packet via channel
        channel.sendPacket(packet);
    }

    /**
     * Sends a packet to all clients connected to the server.
     * 
     * @param packet
     *            GamePacket to send to clients
     * @param channel
     *            GameChannel to use to send the packets
     */
    public void sendPacketToAll(GamePacket packet, GameChannel channel) {
        // Loop through iterator of clients
        Iterator<Client> it = clients.values().iterator();
        while (it.hasNext()) {
            Client client = it.next();

            // don't send a packet back to the client it was received from
            if (client.id != packet.clientId) {

                // create a copy of the packet so that we generate a new packet
                // to send to each client
                GamePacket p = packet.copy();
                p.addr = client.addr;

                // send packet via channel
                channel.sendPacket(p);
                
            }
        }
    }

}
